/****************************************************************************
 * vendor/semidrive/chips/common/arm_mpu.c
 *
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.  The
 * ASF licenses this file to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance with the
 * License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
 * License for the specific language governing permissions and limitations
 * under the License.
 *
 ****************************************************************************/

#include <stddef.h>
#include <stdint.h>
#include <nuttx/irq.h>
#include "mpu.h"
#include "barriers.h"
#include "nuttx/cache.h"

#if defined(EL1_MPU_CFG_ENABLED) && (EL1_MPU_CFG_ENABLED == 1)
void arm_el1_mpu_set_mair(uint8_t index, uint8_t attr)
{
	uint32_t mair_val;
	uint32_t reg_pos;

	if (index > 7U) {
		return;
	}

	if (index < 4) {
		mair_val = CP15_GET(MAIR0);
		reg_pos = (index << 3U);
		mair_val &= (~(0xFFU << reg_pos));
		mair_val |= (attr << reg_pos);
		CP15_SET(MAIR0, mair_val);
	} else {
		index -= 4U;
		mair_val = CP15_GET(MAIR1);
		reg_pos = (index << 3U);
		mair_val &= (~(0xFFU << reg_pos));
		mair_val |= (attr << reg_pos);
		CP15_SET(MAIR1, mair_val);
	}
}

int arm_el1_mpu_enable_region(uint8_t index)
{
	uint32_t reg_val;

	if (index > arm_mpu_get_el1_mpu_region_num()) {
		return -1;
	}

	CP15_SET(PRSELR, index);
	reg_val = CP15_GET(PRLAR) | MPU_PRLAR_MASK_EN;
	CP15_SET(PRLAR, reg_val);

	ARM_DSB();
	ARM_ISB();

	return 0;
}

int arm_el1_mpu_disable_region(uint8_t index)
{
	uint32_t reg_val;

	if (index > arm_mpu_get_el1_mpu_region_num()) {
		return -1;
	}

	CP15_SET(PRSELR, index);
	reg_val = CP15_GET(PRLAR) & (~MPU_PRLAR_MASK_EN);
	CP15_SET(PRLAR, reg_val);

	ARM_DSB();
	ARM_ISB();

	return 0;
}
#endif /* EL1_MPU_CFG_ENABLED */

static int arm_el1_mpu_set_region(uint8_t index, uint32_t base, uint8_t sh, uint8_t ap, uint8_t xn,
	uint32_t limit, uint8_t memattr, uint8_t en)
{
	uint32_t reg_val = 0;

	if (index > arm_mpu_get_el1_mpu_region_num()) {
		return -1;
	}

	CP15_SET(PRSELR, index);
	reg_val = ARM_MPU_PRBAR(base, sh, ap, xn);
	CP15_SET(PRBAR, reg_val);
	reg_val = ARM_MPU_PRLAR(limit, memattr, en);
	CP15_SET(PRLAR, reg_val);

	ARM_DSB();
	ARM_ISB();

	return 0;
}


int arm_el1_mpu_config(const arm_mpu_region_t *regions, uint8_t count)
{
	int ret;
	uint8_t i;

	for (i = 0; i < count; ++i) {
		ret = arm_el1_mpu_set_region(regions[i].number,
				regions[i].base, regions[i].sh, regions[i].ap, regions[i].xn,
				regions[i].limit, regions[i].memattr, regions[i].en);
		if (ret != 0) {
			return -1;
		}
	}

	return 0;
}

static bool arm_el1_mpu_is_area_valid(uint32_t base, uint32_t limit)
{
	uint32_t size = limit - base + 1;

	if (!MPU_IS_ALIGN(base) || (base >= limit)) {
		return false;
	}

	if ((size & ~(ARM_MPU_REGION_ALIGN_SIZE - 1)) != size || size < ARM_MPU_REGION_ALIGN_SIZE) {
		return false;
	}

	return true;
}

int arm_el1_mpu_check_config(const arm_mpu_config_t *config)
{
	if (config == NULL || config->regions == NULL || config->region_num == 0) {
		return -1;
	}

	uint8_t max_region_num = arm_mpu_get_el1_mpu_region_num() - 1;

	if (config->region_num > max_region_num) {
		return -1;
	}

	for (uint8_t i = 0; i < config->region_num; ++i) {
		if (config->regions[i].number > max_region_num) {
			return -1;
		}
		if (!arm_el1_mpu_is_area_valid(config->regions[i].base, config->regions[i].limit)) {
			return -1;
		}
	}
	return 0;
}

void arm_el1_mpu_set_mem_attrs(const arm_mem_attrs_t *mem_attrs)
{
	uint32_t mair0 = ARM_MPU_MAIR_REG(
			mem_attrs->attrs[0][0], mem_attrs->attrs[0][1],
			mem_attrs->attrs[0][2], mem_attrs->attrs[0][3]);
	uint32_t mair1 = ARM_MPU_MAIR_REG(
			mem_attrs->attrs[1][0], mem_attrs->attrs[1][1],
			mem_attrs->attrs[1][2], mem_attrs->attrs[1][3]);

	arm_mpu_set_mairs(mair0, mair1);
}
